package uk.ac.lkl.common.allocator;



import java.util.*;

import uk.ac.lkl.common.allocator.event.*;
import uk.ac.lkl.common.associator.Associator;
import uk.ac.lkl.common.associator.event.*;
import uk.ac.lkl.common.util.ds.AbstractNotifyingList;
import uk.ac.lkl.common.util.ds.NotifyingList;


public class MasterAllocatorModel<M, A, S> extends AllocatorModel<S> {

    /**
     * The allocator model (the master) that this model (the slave) follows.
     * 
     * As the allocation of the master changes, the events from this are used in
     * combination with the associator to update the slave appropriately.
     * 
     */
    private AllocatorModel<M> masterAllocatorModel;


    /**
     * The associator linking master and slave.
     * 
     * Events from this are used to update the slave model appropriately in
     * response to changes in its master.
     * 
     */
    private Associator<M, A, S> associator;


    public MasterAllocatorModel(AllocatorModel<M> masterAllocatorModel,
            Associator<M, A, S> associator) {
        super(masterAllocatorModel.getNumColumns(), associator.getRightList());
        this.masterAllocatorModel = masterAllocatorModel;
        this.associator = associator;
        initialiseObjects();
        // synchronise before add listeners for correct operation
        synchronizeWithMasterSchedule();
        addListeners();

    }


    private void initialiseObjects() {
        AbstractNotifyingList<S> slaveObjects = associator.getRightList();
        for (S slaveObject : slaveObjects)
            addObject(slaveObject);
    }


    private void synchronizeWithMasterSchedule() {
        Collection<M> masterObjects = masterAllocatorModel.getObjects();

        for (M masterObject : masterObjects)
            for (int i = 0; i < masterAllocatorModel.getNumColumns(); i++)
                changeSlaveAllocations(masterObject, i, 0);
    }


    private void addListeners() {
        addMasterBlockListener();
        addAssociatorListener();
    }


    private void addMasterBlockListener() {
        masterAllocatorModel.addAllocatorListener(new AllocatorListener<M>() {

            public void allocationChanged(AllocatorEvent<M> e) {
                processMasterAllocationChanged(e);
            }

        });
    }


    private void processMasterAllocationChanged(AllocatorEvent<M> e) {
        M masterObject = e.getObject();
        int index = e.getIndex();
        changeSlaveAllocations(masterObject, index, e.getOldAllocation());
    }


    /**
     * Add the slave blocks corresponding to the given master block
     * 
     */
    private void changeSlaveAllocations(M masterObject,
                                        int index,
                                        int oldAllocation) {
        if (masterObject == null)
            return;

        int currentAllocation = masterAllocatorModel
                .getAllocation(masterObject, index);

        int allocationDifference = currentAllocation - oldAllocation;

        ArrayList<S> slaveObjects = associator
                .getRightAssociations(masterObject);

        if (slaveObjects == null)
            return;

        for (S slaveObject : slaveObjects)
            changeAllocation(slaveObject, index, allocationDifference);
    }


    private void addAssociatorListener() {
        associator.addAssociatorListener(new AssociatorAdapter<M, A, S>() {

            public void associationAdded(AssociatorEvent<M, A, S> e) {
                processAssociationAdded(e);
            }


            public void associationRemoved(AssociatorEvent<M, A, S> e) {
                processAssociationRemoved(e);
            }

        });
    }


    private void processAssociationAdded(AssociatorEvent<M, A, S> e) {
        M masterObject = e.getLeftElement();
        S slaveObject = e.getRightElement();

        // repeated code with above (to some extent)
        for (int i = 0; i < masterAllocatorModel.getNumColumns(); i++) {
            int allocation = masterAllocatorModel
                    .getAllocation(masterObject, i);
            changeAllocation(slaveObject, i, allocation);
        }
    }


    private void processAssociationRemoved(AssociatorEvent<M, A, S> e) {
        M masterObject = e.getLeftElement();
        S slaveObject = e.getRightElement();

        for (int i = 0; i < masterAllocatorModel.getNumColumns(); i++) {
            int allocation = masterAllocatorModel
                    .getAllocation(masterObject, i);
            changeAllocation(slaveObject, i, -allocation);
        }
    }

}
